/* test_mutex_func_uthread_mutex_lock():
	A test for uthread_mutex_lock(). Checks the regular case + the error
	cases + fairness of the mutex.

	Does:
	* Creates additional threads: 1,2,3.

	* Checks for MUTEX_INVALID error.
	* Checks for MUTEX_UNINITIALIZED error.

	* Intilize a the mutex: mutex_a.
	* Thread 2 locks mutex_a.
	* Thread 0 locks mutex_a.
	* Thread 1 locks mutex_a.
	* Thread 3 locks mutex_a.

	* Then all the threads unlocks mutex_a (in the correct order) and we verift
	that the mutex is fair (FIFO).

	* Thread 3 destroys the mutex_a.

*/


#include <stdio.h>
#include <stdbool.h>

#include "test_general.h"
#include "user_threads.h"
#include "user_mutex.h"


bool test_mutex_func_uthread_mutex_lock();


#define THREADS_NUM 4


uthread_mutex_t mutex_a;
int last_thread_running;
// This can be: 0,1,2,3, or -1 if uninitilized.

static int thread1_start_routine(void * param) {
	TEST_ASSERT (uthread_self() == 1);

	some_long_operation(0.2);

	int tmp = uthread_mutex_lock(mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);

	TEST_ASSERT (0 == last_thread_running);
	last_thread_running = 1;

	tmp = uthread_mutex_unlock(mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);

	thread_ok[1] = true;
	return 0;
}

static int thread2_start_routine(void * param) {
	TEST_ASSERT (uthread_self() == 2);

	int tmp = uthread_mutex_lock(mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);

	TEST_ASSERT (-1 == last_thread_running);
	last_thread_running = 2;

	tmp = uthread_mutex_unlock(mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);

	thread_ok[2] = true;
	return 0;
}

static int thread3_start_routine(void * param) {
	TEST_ASSERT (uthread_self() == 3);

	some_long_operation(0.3);

	int tmp = uthread_mutex_lock(mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);

	TEST_ASSERT (1 == last_thread_running);
	last_thread_running = 3;

	tmp = uthread_mutex_unlock(mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);

	tmp = uthread_mutex_destroy(mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);


	thread_ok[3] = true;
	return 0;
}



bool test_mutex_func_uthread_mutex_lock()
{
	init_individual_test();

	int tmp;

	if (initilize_uthread_in_each_test) {
		tmp = uthread_init();
		TEST_ASSERT(tmp == UTHREAD_SUCCESS);
	}


	/* Checking for MUTEX_INVALID,MUTEX_UNINITIALIZED: */
	tmp = uthread_mutex_lock(NULL);
	TEST_ASSERT (MUTEX_INVALID == tmp);
	tmp = uthread_mutex_lock((uthread_mutex_t)&tmp);
	TEST_ASSERT (MUTEX_UNINITIALIZED == tmp);


	tmp = uthread_mutex_init(&mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);

	thread_id threads[THREADS_NUM];

	threads[0] = uthread_self();
	TEST_ASSERT(threads[0] == 0);

	int FiftyThree = 53;
	last_thread_running = -1;

	threads[1] = uthread_create(thread1_start_routine, &FiftyThree);
	TEST_ASSERT(threads[1] == 1);
	threads[2] = uthread_create(thread2_start_routine, &FiftyThree);
	TEST_ASSERT(threads[2] == 2);
	threads[3] = uthread_create(thread3_start_routine, &FiftyThree);
	TEST_ASSERT(threads[3] == 3);


	some_long_operation(0.1);

	tmp = uthread_mutex_lock(mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);

	TEST_ASSERT (2 == last_thread_running);
	last_thread_running = 0;

	tmp = uthread_mutex_unlock(mutex_a);
	TEST_ASSERT(tmp == MUTEX_SUCCESS);


	tmp = uthread_join(1,NULL);
	TEST_ASSERT(tmp == UTHREAD_SUCCESS);
	tmp = uthread_join(2,NULL);
	TEST_ASSERT(tmp == UTHREAD_SUCCESS);
	tmp = uthread_join(3,NULL);
	TEST_ASSERT(tmp == UTHREAD_SUCCESS);

	TEST_ASSERT (3 == last_thread_running);


	thread_ok[0] = true;
	TEST_ASSERT(are_all_threads_ok(THREADS_NUM));

	return true;
}

